﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Runtime.Serialization;
using System.Threading.Tasks;
//using System.Timers;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using EpiNEXT.Services.Data;

namespace EpiNEXT.Services
{
    /// <summary>
    /// A type to represent EpiNEXT project files
    /// </summary>
    //[Serializable]
    [XmlRoot("Project")]
    public class Project : IDisposable
    {
        #region Members
        private string _title = String.Empty;
        private string _summary = String.Empty;
        private Form _defaultForm = null;
        private string _checkForUpdatesMode = String.Empty;
        //private Timer _updateTimer = new Timer(5000);
        private static object _formsLock = new object();
        private static object _dataGridTablesLock = new object();
        private static object _codeTablesLock = new object();
        #endregion // Members

        #region Properties
        /// <summary>
        /// The currently selected field
        /// </summary>
        [XmlIgnore]
        public string CurrentField { get; set; }

        /// <summary>
        /// A timer used to check for database updates
        /// </summary>
        //[XmlIgnore]
        //protected Timer UpdateTimer
        //{
        //    get
        //    {
        //        return this._updateTimer;
        //    }
        //    set
        //    {
        //        this._updateTimer = value;
        //    }
        //}

        /// <summary>
        /// The information for the file that represents this project on disk
        /// </summary>
        //[XmlIgnore]
        //public FileInfo ProjectFileInfo { get; set; }

        [XmlIgnore]
        public string ProjectFilePath { get; set; }

        /// <summary>
        /// The location of the data (e.g. in the same file as the project, in 
        /// a MSSQL server database; generally this will be either a file path
        /// or a connection string)
        /// </summary>
        [XmlElement("DataLocation")]
        public string DataLocation { get; set; }

        /// <summary>
        /// The data driver for the data storage; e.g. XmlDriver, SqlDriver
        /// </summary>
        [XmlElement("DataDriver")]
        public string DataDriverString { get; set; }

        /// <summary>
        /// The database
        /// </summary>
        [XmlIgnore]
        public IDbDriver Database { get; set; }

        /// <summary>
        /// The mode to use for checking for updates to the database
        /// </summary>
        [XmlElement("CheckForUpdatesMode")]
        public string CheckForUpdatesMode
        {
            get
            {
                return this._checkForUpdatesMode;
            }
            set
            {
                this._checkForUpdatesMode = value;
            }
        }

        /// <summary>
        /// Special code list for Yes/No
        /// </summary>
        [XmlIgnore]
        public CodeList CodeListYN
        {
            get;
            set;
        }

        /// <summary>
        /// Special code list for Yes/No
        /// </summary>
        [XmlIgnore]
        public CodeList CodeListYNU
        {
            get;
            set;
        }

        /// <summary>
        /// A list of pages in the form
        /// </summary>
        [XmlArray("Lists")]
        [XmlArrayItem("List", typeof(CodeList))]
        public ObservableCollection<CodeList> CodeLists { get; set; }

        /// <summary>
        /// The mode to use for checking for updates to the database
        /// </summary>
        //[XmlIgnore]
        //internal DataDriverUpdateMode DataDriverUpdateMode
        //{
        //    get
        //    {
        //        switch (CheckForUpdatesMode.ToLower())
        //        {
        //            case "timer":
        //                return Core.DataDriverUpdateMode.Timer;
        //            case "servicebroker":
        //                return Core.DataDriverUpdateMode.ServiceBroker;
        //            default:
        //            case "none":
        //                return Core.DataDriverUpdateMode.None;
        //        }
        //    }
        //}

        /// <summary>
        /// The ID that represents this project
        /// </summary>
        [XmlElement("Id")]
        public Guid ProjectId { get; set; }

        /// <summary>
        /// The author of this project
        /// </summary>
        [XmlElement("Author")]
        public string Author { get; set; }

        /// <summary>
        /// The date/time the project was created
        /// </summary>
        [XmlIgnore]
        public DateTime DateCreated { get; set; }

        /// <summary>
        /// The date/time the project was last saved
        /// </summary>
        [XmlIgnore]
        public DateTime DateLastSaved { get; set; }

        [XmlElement]
        public long LastSaved
        {
            get { return DateLastSaved.Ticks; }
            set { DateLastSaved = new DateTime(value); }
        }

        [XmlElement]
        public long Created
        {
            get { return DateCreated.Ticks; }
            set { DateCreated = new DateTime(value); }
        }

        /// <summary>
        /// The title for the project, which becomes the file name
        /// </summary>
        [XmlElement("Title")]
        public string Title
        {
            get
            {
                return this._title;
            }
            set
            {
                if (String.IsNullOrEmpty(value))
                {
                    throw new InvalidOperationException("Project title cannot be blank.");
                }
                else if (value.Length > 64)
                {
                    throw new InvalidOperationException("Project title cannot exceed 64 characters.");
                }
                else
                {
                    double firstChar = -1;
                    bool success = double.TryParse(value[0].ToString(), out firstChar);
                    if (success)
                    {
                        throw new InvalidOperationException("Project title may not start with a digit.");
                    }

                    this._title = value;
                }
            }
        }

        /// <summary>
        /// The short (255 character) summary of the project
        /// </summary>
        [XmlElement("Summary")]
        public string Summary
        {
            get
            {
                return this._summary;
            }
            set
            {
                if (value.Length > 255)
                {
                    throw new InvalidOperationException("Project summary cannot exceed 255 characters.");
                }
                else
                {
                    this._summary = value;
                }
            }
        }

        /// <summary>
        /// A longer, fuller description of the project
        /// </summary>
        [XmlElement("Description")]
        public string Description { get; set; }

        /// <summary>
        /// The mode the project should open in
        /// </summary>
        //[XmlElement("OpenMode")]
        //public ProjectOpenMode OpenMode { get; set; }

        /// <summary>
        /// The 90x90 icon that should be used for the project
        /// </summary>
        //[XmlElement("Icon")]
        //public byte[] Icon { get; set; }

        /// <summary>
        /// The list of forms contained in this project
        /// </summary>
        [XmlArray("Forms")]
        [XmlArrayItem("Form", typeof(Form))]
        public ObservableCollection<Form> Forms { get; set; }

        /// <summary>
        /// The list of data grid tables contained in this project
        /// </summary>
        [XmlArray("DataGridTables")]
        [XmlArrayItem("DataGridTable", typeof(DataGridTable))]
        public ObservableCollection<DataGridTable> DataGridTables { get; set; }

        /// <summary>
        /// The default form in this project
        /// </summary>
        [XmlIgnore]
        public Form DefaultForm
        {
            get
            {
                return this._defaultForm;
            }
            set
            {
                if (!Forms.Contains(value))
                {
                    throw new InvalidOperationException("The default form specified is not part of the collection of forms in this project.");
                }
                else
                {
                    this._defaultForm = value;
                }
            }
        }
        #endregion // Properties

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        public Project()
        {
            this.Title = "New Project";
            this.Author = String.Empty;
            this.ProjectId = Guid.NewGuid();
            this.DateCreated = DateTime.Now;
            this.DateLastSaved = DateTime.Now;
            this.Description = String.Empty;
            this.Summary = String.Empty;
            this.Forms = new ObservableCollection<Form>();
            this.DataGridTables = new ObservableCollection<DataGridTable>();
            this.CodeLists = new ObservableCollection<CodeList>();
            this.CurrentField = String.Empty;
            CreateYesNoCodeLists();
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="fileName">The file name of where the project file resides</param>
        public Project(string fileName)
        {
            //ProjectFileInfo = new FileInfo(fileName);
            this.ProjectFilePath = fileName;
            this.Title = "New Project";
            this.Author = String.Empty;
            this.ProjectId = Guid.NewGuid();
            this.DateCreated = DateTime.Now;
            this.DateLastSaved = DateTime.Now;
            this.Description = String.Empty;
            this.Summary = String.Empty;
            this.Forms = new ObservableCollection<Form>();
            this.DataGridTables = new ObservableCollection<DataGridTable>();
            this.CodeLists = new ObservableCollection<CodeList>();
            this.CurrentField = String.Empty;
            CreateYesNoCodeLists();
        }
        #endregion // Constructors

        #region Methods
        /// <summary>
        /// Creates the Yes/No and Yes/No/Unknown code lists.
        /// </summary>
        private void CreateYesNoCodeLists()
        {
            // TODO: Bring in Y/N/U values from translation lookup
            CodeListYN = new CodeList();
            CodeListYN.Name = "SpecialYesNoList";
            CodeListYN.Mode = CodeListMode.Coded;
            CodeListYN.Concatenate = false;

            CodeListValue listYN_Yes = new CodeListValue();
            listYN_Yes.Display = "Yes";
            listYN_Yes.Store = "1";
            listYN_Yes.Owner = CodeListYN;

            CodeListValue listYN_No = new CodeListValue();
            listYN_No.Display = "No";
            listYN_No.Store = "2";
            listYN_No.Owner = CodeListYN;

            CodeListYN.CodeListValues.Add(listYN_Yes);
            CodeListYN.CodeListValues.Add(listYN_No);

            CodeListYNU = new CodeList();
            CodeListYNU.Name = "SpecialYesNoUnknownList";
            CodeListYNU.Mode = CodeListMode.Coded;
            CodeListYNU.Concatenate = false;

            CodeListValue listYNU_Yes = new CodeListValue();
            listYNU_Yes.Display = "Yes";
            listYNU_Yes.Store = "1";
            listYNU_Yes.Owner = CodeListYNU;

            CodeListValue listYNU_No = new CodeListValue();
            listYNU_No.Display = "No";
            listYNU_No.Store = "2";
            listYNU_No.Owner = CodeListYNU;

            CodeListValue listYNU_Unknown = new CodeListValue();
            listYNU_Unknown.Display = "Unknown";
            listYNU_Unknown.Store = "9";
            listYNU_Unknown.Owner = CodeListYNU;

            CodeListYNU.CodeListValues.Add(listYNU_Yes);
            CodeListYNU.CodeListValues.Add(listYNU_No);
            CodeListYNU.CodeListValues.Add(listYNU_Unknown);
        }

        /// <summary>
        /// Saves the project to disk.
        /// </summary>
        public void Save()
        {
            this.Database.SaveAllRecords(this);
        }

        /// <summary>
        /// Initializes a timer that can be used to periodically check the underlying database for updates
        /// </summary>
        /// <param name="interval">The interval (in milliseconds) to check for updates</param>
        protected internal void InitializeTimer(double interval = 5000)
        {
            if (interval == null)
            {
                throw new ArgumentNullException("The interval for the database timer cannot be null.");
            }
            else if (interval < 1000)
            {
                throw new ArgumentOutOfRangeException("The timer interval for database checks should not be less than 1000 milliseconds.");
            }
            else if(interval > 1800000) 
            {
                throw new ArgumentOutOfRangeException("The timer interval for database checks should not exceed 30 minutes.");
            }

            //this.UpdateTimer.Interval = interval;
            //this.UpdateTimer.Elapsed += UpdateTimer_Elapsed;
            //this.UpdateTimer.Start();
        }

        /// <summary>
        /// Sets a special binding operation to each of the record collections in each form within the project
        /// that enables those collections to be accessed on a thread other than the UI thread.
        /// </summary>
        //public void InitalizeRecordCollectionSynchronization()
        //{
        //    foreach(Form form in this.Forms) 
        //    {
        //        form.InitalizeCollectionSynchronization();
        //    }

        //    System.Windows.Data.BindingOperations.EnableCollectionSynchronization(Forms, _formsLock);
        //    System.Windows.Data.BindingOperations.EnableCollectionSynchronization(DataGridTables, _dataGridTablesLock);
        //    System.Windows.Data.BindingOperations.EnableCollectionSynchronization(CodeLists, _codeTablesLock);
        //}

        //private void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
        //{
        //    Parallel.ForEach(Forms, form =>
        //    {
        //        if (Database is SqlDriverBase)
        //        {
        //            (Database as SqlDriverBase).CheckForUpdates(form);
        //        }
        //    });
        //}

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources
                //this.UpdateTimer.Elapsed -= UpdateTimer_Elapsed;
                //this.UpdateTimer.Dispose();
                (this.Database as DbDriverBase).Dispose();
                foreach (Form form in this.Forms)
                {
                    form.Records.Clear();
                }

                Forms.Clear();
            }
            // free native resources if there are any.
        }

        public Form GetFormById(Guid id)
        {
            foreach (Form form in this.Forms)
            {
                if (form.FormId.Equals(id))
                {
                    return form;
                }
            }
            throw new InvalidOperationException("Requested form does not exist.");
        }

        public DataGridTable GetDataGridTableById(Guid id)
        {
            foreach (DataGridTable dgTable in this.DataGridTables)
            {
                if (dgTable.FormId.Equals(id))
                {
                    return dgTable;
                }
            }
            throw new InvalidOperationException("Requested data grid table does not exist.");
        }

        public CodeList GetCodeListById(Guid id)
        {
            foreach (CodeList codeList in this.CodeLists)
            {
                if (codeList.Id.Equals(id))
                {
                    return codeList;
                }
            }

            if (CodeListYN.Id.Equals(id)) { return CodeListYN; }
            if (CodeListYNU.Id.Equals(id)) { return CodeListYNU; }

            throw new InvalidOperationException("Requested code list does not exist.");
        }
        #endregion // Methods
    }
}
